package net.sf.fmj.ejmf.toolkit.gui.controls;

import java.awt.BorderLayout;
import java.awt.Component;

import javax.media.Control;
import javax.media.Controller;
import javax.media.ControllerErrorEvent;
import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.StartEvent;
import javax.media.StopEvent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.EtchedBorder;
import javax.swing.border.TitledBorder;

import net.sf.fmj.ejmf.toolkit.util.SourcedTimer;
import net.sf.fmj.ejmf.toolkit.util.SourcedTimerEvent;
import net.sf.fmj.ejmf.toolkit.util.SourcedTimerListener;
import net.sf.fmj.ejmf.toolkit.util.TimeSource;

/**
 * A read-only Control that displays media time.
 * <p>
 * Creates a SourcedTimer to generate ticks that trigger Control to look at
 * media time, convert it to a string and display it.
 * <p>
 * Registers as SourcedTimerListener and ControllerListener. As
 * ControllerListener, TimerDisplayControl listens for start/restart and stop
 * events to correctly turn SourcedTimer on and off.
 * 
 * @see net.sf.fmj.ejmf.toolkit.util.SourcedTimer
 * @see net.sf.fmj.ejmf.toolkit.util.SourcedTimerListener
 * @see net.sf.fmj.ejmf.toolkit.util.TimeSource
 */

public class TimeDisplayControl implements Control, SourcedTimerListener,
		TimeSource, ControllerListener {

	private JLabel timerField = new JLabel("0:00:00", JLabel.RIGHT);
	private JPanel controlComponent = new JPanel();

	private String timeVal;

	private SourcedTimer timer;
	private Controller controller;
	private long divisor;

	/**
	 * Create a TimeDisplayControl for displaying the current media time of the
	 * Controller passed as an argument.
	 * 
	 * @param controller
	 *            The Controller with which to associate control.
	 */
	public TimeDisplayControl(Controller controller) {
		this.controller = controller;

		// Create a timer and listener for 'ticks'
		timer = new SourcedTimer(this);
		timer.addSourcedTimerListener(this);

		// SourcedTimer units divided by divisor
		// equals seconds.
		divisor = timer.getConversionDivisor();

		controller.addControllerListener(this);

		setUpControlComponent();

		// Initialize time label
		timeVal = convertTime(getTime(), divisor);
		loadTime();
	}

	/**
	 * Create a TimeDisplayControl for displaying the current media time of the
	 * Controller passed as an argument.
	 * <p>
	 * The <code>timer</code> is used as the SourcedTimer. This form of the
	 * constructor allows reuse of an existing SourcedTimer.
	 * 
	 * @param controller
	 *            javax.media.Controller
	 * @param timer
	 *            ejmf.toolkit.util.SourcedTimer
	 * 
	 * @see net.sf.fmj.ejmf.toolkit.util.SourcedTimer
	 */
	public TimeDisplayControl(Controller controller, SourcedTimer timer) {
		this.controller = controller;

		// Create a timer and listener for 'ticks'
		this.timer = timer;
		timer.addSourcedTimerListener(this);

		// SourcedTimer units divided by divisor
		// equals seconds.
		divisor = timer.getConversionDivisor();

		controller.addControllerListener(this);

		setUpControlComponent();

		// Initialize time label
		timeVal = convertTime(getTime(), divisor);
		loadTime();
	}

	/**
	 * Return the control component.
	 * 
	 * @return java.awt.Component
	 */
	public Component getControlComponent() {
		return controlComponent;
	}

	/**
	 * Build border and title the control component.
	 */
	private void setUpControlComponent() {
		JLabel timeLabel = new JLabel("Media Time:", JLabel.RIGHT);
		JPanel mainPanel = new JPanel();
		int GAP = 10;

		Border emptyBorder = new EmptyBorder(GAP, GAP, GAP, GAP);

		Border etchedBorder = new CompoundBorder(new EtchedBorder(),
				emptyBorder);

		Border titledBorder = new TitledBorder(etchedBorder,
				"Time Display Control");

		mainPanel.setBorder(titledBorder);
		mainPanel.setLayout(new BorderLayout(GAP, GAP));
		mainPanel.add(timeLabel, BorderLayout.CENTER);
		mainPanel.add(timerField, BorderLayout.EAST);

		controlComponent.add(mainPanel);
	}

	/**
	 * Input value expected in nanoseconds and converted to a String in h:mm:ss
	 * format.
	 * 
	 * @param longTime
	 *            time in unit such that <code>longTime/d</code> results in
	 *            seconds.
	 * @param value
	 *            such that <code>longTime/d</code> results in seconds.
	 * 
	 * @return java.awt.String representation of converted time
	 */
	private String convertTime(long longTime, long d) {
		int hr = 0;
		int min = 0;
		long sec = longTime / d;

		min = (int) (sec / 60);

		if (min > 0) {
			if (min > 60) {
				hr = min / 60;
				min = min % 60;
			}
			sec = sec % 60;
		}

		String t = hr + ":";
		if (min < 10) {
			t = t + 0 + min + ":";
		} else {
			t = t + min + ":";
		}

		if (sec < 10) {
			t = t + 0 + sec;
		} else {
			t = t + sec;
		}
		return t;
	}

	// All but writing to text field is done before
	// call to loadTime. We don't want to do any conversion
	// on event thread.
	/*
	 * Display the current media time.
	 */

	private void loadTime() {
		timerField.setText(timeVal);
	}

	/*
	 * Write time to GUI. Convenience class for execution on AWT thread.
	 */
	class LoadTimeThread implements Runnable {
		public void run() {
			loadTime();
		}
	}

	// ////////// SourcedTimerListener interface ////////////

	/**
	 * Respond to 'tick' from SourcedTimer.
	 * <p>
	 * 
	 * @param e
	 *            a SourcedTimerEvent containing TimeSource that generated tick.
	 */
	public void timerUpdate(SourcedTimerEvent e) {
		timeVal = convertTime(getTime(), divisor);
		SwingUtilities.invokeLater(new LoadTimeThread());
	}

	// /////// ControllerListener Interface /////////////

	/**
	 * Listen for start/stop events and start/stop SourceTimer in response.
	 * 
	 * @param e
	 *            A controller event used to determine start/stop state of
	 *            timer.
	 */
	public void controllerUpdate(ControllerEvent e) {
		if (e instanceof StopEvent || e instanceof ControllerErrorEvent) {
			timer.stop();
		} else if (e instanceof StartEvent) {
			timer.start();
		}
	}

	// //////// TimeSource Interface //////////////

	/**
	 * Report media time from associated Controller.
	 * <p>
	 * 
	 * @return media time in nanoseconds
	 */
	public long getTime() {
		return controller.getMediaNanoseconds();
	}

	/**
	 * Report value such that <code>getTime()/number</code> equals seconds.
	 * <p>
	 * 
	 * @return a long such that <code>getTime()/number</code> equals seconds.
	 */
	public long getConversionDivisor() {
		return TimeSource.NANOS_PER_SEC;
	}

}
